package parser;

import java.util.ArrayList;

import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;
import util.AST.Program;
import util.AST.Command.Command;
import util.AST.Command.FunctionBody;
import util.AST.Command.FunctionDeclaration;
import util.AST.Command.VariableDeclaration;
import util.AST.Expression.BinaryExpression;
import util.AST.Expression.BooleanUnaryExpression;
import util.AST.Expression.Expression;
import util.AST.Expression.IdentifierUnaryExpression;
import util.AST.Expression.NumberUnaryExpression;
import util.AST.RHS.CallStatementRHS;
import util.AST.RHS.ExpressionRHS;
import util.AST.RHS.RHS;
import util.AST.Statement.AssignStatement;
import util.AST.Statement.BreakStatement;
import util.AST.Statement.CallStatement;
import util.AST.Statement.ContinueStatement;
import util.AST.Statement.IfElseStatement;
import util.AST.Statement.PrintlnStatement;
import util.AST.Statement.ReturnStatement;
import util.AST.Statement.Statement;
import util.AST.Statement.WhileStatement;
import util.AST.Terminal.BooleanValue;
import util.AST.Terminal.Identifier;
import util.AST.Terminal.NumberValue;
import util.AST.Terminal.Operator;
import util.AST.Terminal.Type;

/**
 * Parser class
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Parser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	/**
	 * Parser constructor
	 */
	public Parser() {
		// Initializes the scanner object
		this.scanner = new Scanner();
	}
	
	public Parser(String file) {
		// Initializes the scanner object
		this.scanner = new Scanner(file);
	}
	
	/**
	 * Return if token is Type.
	 * @param t
	 * @return
	 */
	private boolean isType(Token t){
		if (t.getKind() == GrammarSymbols.INT || t.getKind() == GrammarSymbols.DOUBLE ||
				t.getKind() == GrammarSymbols.VOID || t.getKind() == GrammarSymbols.BOOLEAN)
			return true;
		else
			return false;
	}
	
	/**
	 *  Program -> (Type "identifier" ( ";" | "(" (Parameters | empty) ")" "{" FunctionBody "}" ))* eot
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private Program parseProgram() throws SyntacticException, LexicalException{
		acceptIt(); // Load first token in file.
		ArrayList<Command> commands = new ArrayList<Command>();
		if(isType(currentToken)){
			while(isType(currentToken)){
				Type type = parseType();
				Identifier id = new Identifier(currentToken.getSpelling()); 
				accept(GrammarSymbols.ID);
				if(currentToken.getKind() == GrammarSymbols.SEMICOLON){
					acceptIt();
					commands.add(new VariableDeclaration(type,id));
				}else{
					ArrayList<VariableDeclaration> parameters = new ArrayList<VariableDeclaration>();
					accept(GrammarSymbols.LPAR);
					if(currentToken.getKind() != GrammarSymbols.RPAR){
						parameters = parseParameters();
					}
					accept(GrammarSymbols.RPAR);
					accept(GrammarSymbols.LBRACKET);
					FunctionBody functionBody = parseFunctionBody();
					accept(GrammarSymbols.RBRACKET);
					commands.add(new FunctionDeclaration(type,id,parameters,functionBody));
				}	
			}
		}
		accept(GrammarSymbols.EOT);
		Program program = new Program(commands);
		return program;
	} 
	
	/**
	 * Type -> "void" | "int" | "double" | "boolean"
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private Type parseType() throws SyntacticException, LexicalException{
		Type type;
		if(currentToken.getKind() == GrammarSymbols.BOOLEAN ){
			type = new Type(currentToken.getSpelling());
			accept(GrammarSymbols.BOOLEAN);
			return type;
		}else if(currentToken.getKind() == GrammarSymbols.INT){
			type = new Type(currentToken.getSpelling());
			accept(GrammarSymbols.INT);
			return type;
		}else if(currentToken.getKind() == GrammarSymbols.DOUBLE){
			type = new Type(currentToken.getSpelling());
			accept(GrammarSymbols.DOUBLE);
			return type;
		}else{
			type = new Type(currentToken.getSpelling());
			accept(GrammarSymbols.VOID);
			return type;
			}
	}
	
	/**
	 * FunctionBody -> (Type "identifier" ";")* Statements
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private FunctionBody parseFunctionBody() throws SyntacticException, LexicalException{
		ArrayList<VariableDeclaration> variableDeclaration = new ArrayList<VariableDeclaration>();
		while(isType(currentToken)){
			Type type = new Type(currentToken.getSpelling());
			accept(currentToken.getKind());
			Identifier id = new Identifier(currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			variableDeclaration.add(new VariableDeclaration(type,id));
			accept(GrammarSymbols.SEMICOLON);
		}
		ArrayList<Statement> statement = parseStatements();
		return new FunctionBody(variableDeclaration,statement);
	}
	
	/**
	 * Statements -> Statement
	 * @throws SyntacticException 
	 * @throws LexicalException *
	 */
	private ArrayList<Statement> parseStatements() throws SyntacticException, LexicalException{
		ArrayList<Statement> statement = new ArrayList<Statement>();
		while(currentToken.getKind() == GrammarSymbols.ID ||
				currentToken.getKind() == GrammarSymbols.IF ||
				currentToken.getKind() == GrammarSymbols.WHILE ||
				currentToken.getKind() == GrammarSymbols.RETURN ||
				currentToken.getKind() == GrammarSymbols.BREAK ||
				currentToken.getKind() == GrammarSymbols.CONTINUE ||
				currentToken.getKind() == GrammarSymbols.PRINTLN){
			statement.add(parseStatement());
		}
		return statement;
	}
	
	/**
	 * Statement -> "identifier" ( "(" (Arguments | empty) ")" | "=" RHS) ";" 
	 * | "if" "(" Expression ")" "{" Statements "}" (empty | "else" "{" Statements "}") 
	 * | "while" "(" Expression ")" "{" Statements "}" 
	 * | "return" (empty | Expression) ";" 
	 * | "break" ";" 
	 * | "continue" ";" 
	 * | "println" "(" "identifier" ")" ";"
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private Statement parseStatement() throws SyntacticException, LexicalException{
		if (currentToken.getKind() == GrammarSymbols.ID){ //"identifier" ( "(" (Arguments | empty) ")" | "=" RHS) ";" 
			Identifier id = new Identifier(currentToken.getSpelling());
			RHS rhs = null;
			accept(GrammarSymbols.ID);
			if(currentToken.getKind() == GrammarSymbols.LPAR){ // criando lista para receber os argumentos da funcao
				acceptIt();
				ArrayList<Identifier> arguments = new ArrayList<Identifier>();
				if(currentToken.getKind() == GrammarSymbols.ID){
					arguments = parseArguments();
				}
				accept(GrammarSymbols.RPAR);
				accept(GrammarSymbols.SEMICOLON);
				return new CallStatement(id, arguments);
			}else{
				accept(GrammarSymbols.ASSIGN);
				rhs = parseRHS();
				accept(GrammarSymbols.SEMICOLON);
				return new AssignStatement(id, rhs);
			}	
		}
		else if(currentToken.getKind() == GrammarSymbols.IF){ //"if" "(" Expression ")" "{" Statements "}" (empty | "else" "{" Statements "}") 
			Expression condition = null;
			ArrayList<Statement> ifStatements = new ArrayList<Statement>();
			ArrayList<Statement> elseStatements = new ArrayList<Statement>();
			accept(GrammarSymbols.IF);
			accept(GrammarSymbols.LPAR);
			condition = parseExpression(); //fazer
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			ifStatements = parseStatements();
			accept(GrammarSymbols.RBRACKET);
			if(currentToken.getKind() == GrammarSymbols.ELSE){
				acceptIt();
				accept(GrammarSymbols.LBRACKET);
				elseStatements = parseStatements();
				accept(GrammarSymbols.RBRACKET);
			}
			return new IfElseStatement(condition, ifStatements, elseStatements);
		}else if(currentToken.getKind() == GrammarSymbols.WHILE){ //"while" "(" Expression ")" "{" Statements "}"
			Expression condition = null;
			ArrayList<Statement> statements= new ArrayList<Statement>();
			accept(GrammarSymbols.WHILE);
			accept(GrammarSymbols.LPAR);
			condition = parseExpression();
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			statements = parseStatements();
			accept(GrammarSymbols.RBRACKET);
			return new WhileStatement(condition, statements); 
		}else if(currentToken.getKind() == GrammarSymbols.RETURN){ // "return" (empty | Expression) ";" 
			acceptIt();
			Expression expression = null;
			if(currentToken.getKind() == GrammarSymbols.SEMICOLON){
				acceptIt();
			}else{
				expression = parseExpression();
				accept(GrammarSymbols.SEMICOLON);	
			}
			//System.out.println(expression.toString());
			return new ReturnStatement(expression);
			
		}else if(currentToken.getKind() == GrammarSymbols.BREAK){ // "break" ";" 
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			return new BreakStatement();
		}else if(currentToken.getKind() == GrammarSymbols.CONTINUE){ // "continue" ";"
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			return new ContinueStatement();
		}else{ 												//"println" "(" "identifier" ")" ";"
			accept(GrammarSymbols.PRINTLN);
			accept(GrammarSymbols.LPAR);
			Identifier variable = new Identifier(currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.SEMICOLON);
			return new PrintlnStatement(variable);
		}
	}
	
	/**
	 * Arguments -> "identifier" (empty | "," Arguments)
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private ArrayList<Identifier> parseArguments() throws SyntacticException, LexicalException{ // feita
		ArrayList<Identifier> ids = new ArrayList<Identifier>();
		ids.add(new Identifier(currentToken.getSpelling()));
		accept(GrammarSymbols.ID);
		if(currentToken.getKind() == GrammarSymbols.COMMA){
			acceptIt();
			ids.addAll(parseArguments());
		}
		return ids;
	}
	
	/**
	 * RHS -> "(" Expression ")" | "identifier" "(" (Arguments | empty) ")"
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private RHS parseRHS() throws SyntacticException, LexicalException{
		if(currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			ExpressionRHS expressionRhs = new ExpressionRHS(parseExpression());
			accept(GrammarSymbols.RPAR);
			return expressionRhs;
		}else {
			Identifier function = new Identifier(currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.LPAR);
			ArrayList<Identifier> arguments = new ArrayList<Identifier>();
			if(currentToken.getKind() == GrammarSymbols.ID) 
				arguments = parseArguments();
			accept(GrammarSymbols.RPAR);
			return new CallStatementRHS(new CallStatement(function, arguments)); 
		}
	}
	
	/**
	 * Parameters -> Type "identifier" (empty | "," Parameters)
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private ArrayList<VariableDeclaration> parseParameters() throws SyntacticException, LexicalException{
		ArrayList<VariableDeclaration> list = new ArrayList<VariableDeclaration>();
		Type type = parseType();
		Identifier id = new Identifier(currentToken.getSpelling());
		accept(GrammarSymbols.ID);
		list.add(new VariableDeclaration(type,id));
		while(currentToken.getKind() == GrammarSymbols.COMMA){
			acceptIt();
			list.addAll(parseParameters());
		}
		return list;
	}

	/**
	 * Expression -> "(" Expression ( + | - | * | / | == | != | > | >= | < | <= ) Expression ")" | Value
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private Expression parseExpression() throws SyntacticException, LexicalException{
		if(currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			Expression leftExpression = parseExpression();
			Operator operator = null;
			operator = new Operator(currentToken.getSpelling());
			if(currentToken.getKind() == GrammarSymbols.PLUS){
				acceptIt();	
			}
			else if(currentToken.getKind() == GrammarSymbols.MINUS){
				acceptIt();
			}
			else if(currentToken.getKind() == GrammarSymbols.MULT){
				acceptIt();
			}
			else if(currentToken.getKind() == GrammarSymbols.DIV){
				acceptIt();
			}
			else if(currentToken.getKind() == GrammarSymbols.EQUAL){
				acceptIt();
			}
			else if(currentToken.getKind() == GrammarSymbols.NOTEQUAL){
				acceptIt();
			}
			else if(currentToken.getKind() ==  GrammarSymbols.GREATERTHAN){
				acceptIt();
			}
			else if(currentToken.getKind() ==  GrammarSymbols.GREATEREQUALTHAN){
				acceptIt();
			}
			else if(currentToken.getKind() ==  GrammarSymbols.LESSERTHAN){
				acceptIt();
			}
			else
				accept(GrammarSymbols.LESSEREQUALTHAN);
			Expression rightExpression = parseExpression();
			accept(GrammarSymbols.RPAR);
			return new BinaryExpression(leftExpression, operator, rightExpression);
		}else
			return parseValue();
	}
	
	/**
	 * Value -> "identifier" | "number" | "false" | "true"
	 * @throws SyntacticException 
	 * @throws LexicalException 
	 */
	private Expression parseValue() throws SyntacticException, LexicalException{
		if(currentToken.getKind() == GrammarSymbols.ID){ 
			Identifier id = new Identifier(currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			return new IdentifierUnaryExpression(id);
		}
		else if(currentToken.getKind() == GrammarSymbols.NUMBER) {
			NumberValue value = new NumberValue(currentToken.getSpelling());
			accept(GrammarSymbols.NUMBER);
			return new NumberUnaryExpression(value);
		}
		else if(currentToken.getKind() == GrammarSymbols.FALSE){
			BooleanValue boolvalue = new BooleanValue(currentToken.getSpelling());
			accept(GrammarSymbols.FALSE);
			return new BooleanUnaryExpression(boolvalue);
		}
		else{
			BooleanValue boolvalue = new BooleanValue(currentToken.getSpelling());
			accept(GrammarSymbols.TRUE);
			return new BooleanUnaryExpression(boolvalue);
		}
	}
		
	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ //TODO
	private void accept(int kind) throws SyntacticException, LexicalException {
		// If the current token kind is equal to the expected
		if(currentToken.getKind()== kind){
			acceptIt();
		}else
			throw new SyntacticException("Token unexpected!",currentToken);
	}
	
	/**
	 * Gets next token
	 * @throws LexicalException 
	 */ //TODO
	private void acceptIt() throws LexicalException {
		currentToken = scanner.getNextToken();
	}

	/**
	 * Verifies if the source program is syntactically correct
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ //TODO
	public AST parse() throws SyntacticException, LexicalException {
		return parseProgram();
	}
}
